/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.muni.fi.pb138.xml.wrda.dao;

import com.thoughtworks.xstream.XStream;
import cz.muni.fi.pb138.xml.wrda.core.XSD;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.basex.core.BaseXException;
import org.basex.core.Context;
import org.basex.core.cmd.CreateDB;
import org.basex.core.cmd.Open;
import org.basex.core.cmd.XQuery;
import org.basex.query.QueryException;
import org.basex.query.QueryProcessor;

/**
 *
 * @author Ondřej Chrastina 430596@mail.muni.cz
 */
public class XSDDaoImpl implements XSDDao {

    private Context baseXContext;
    private XStream xstream;

    public XSDDaoImpl() {

        Properties properties = new Properties();
        try {
            properties.load(this.getClass().getResourceAsStream("/config.properties"));
        } catch (IOException ex) {
            Logger.getLogger(XSDDaoImpl.class.getName()).log(Level.SEVERE, "Error while loading properties.", ex);
        }
        String xsdStorage = properties.getProperty("db.xsd.name");

        baseXContext = new Context();
        //createDB(xsdStorage);
        xstream = new XStream();
        setxStreamAliases();

        try {
            new Open(xsdStorage).execute(baseXContext);
        } catch (BaseXException ex) {
            Logger.getLogger(XSDDaoImpl.class.getName()).log(Level.SEVERE, "Error while opening XSD database by context.", ex);
        }
    }

    @Override
    public void save(XSD data) {
        String query = MessageFormat.format("insert node {0} into {1}", xstream.toXML(data), "/" + XSDEntries.class.getSimpleName());
        try {
            new XQuery(query).execute(baseXContext);
        } catch (BaseXException ex) {
            Logger.getLogger(XSDDaoImpl.class.getName()).log(Level.SEVERE, "Error while saving xsd entry.", ex);
        }

    }

    @Override
    public void delete(XSD data) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public List<XSD> getAllEntries() {
        String baseXResult = "";
//        String query = MessageFormat.format(
//                "<{0}> '{' for $xsd in distinct-values(/{0}/{1}) return $xsd '}' </{0}>", XSDEntries.class.getSimpleName(), XSD.class.getSimpleName());
        String query = MessageFormat.format(
                "<{0}> '{' for $xsd in /{0}/{1} return $xsd '}' </{0}>", XSDEntries.class.getSimpleName(), XSD.class.getSimpleName());

        try {
            baseXResult = new XQuery(query).execute(baseXContext);
        } catch (BaseXException ex) {
            Logger.getLogger(XSDDaoImpl.class.getName()).log(Level.SEVERE, "BaseX query failed in finding all queries.", ex);
        }

        //empty database
        if (baseXResult.equals("")) {
            return new ArrayList<XSD>();
        }

        XSDEntries ret = (XSDEntries) xstream.fromXML(baseXResult);

        return ret.list;
    }

    @Override
    public Object getAllEntriesDistinctNames() {
        String baseXResult = "";
//        <XSDEntries> { for $xsd in distinct-values(/XSDEntries/XSD/fileName/text()) return  (/XSDEntries/XSD[fileName = $xsd])[1] } </XSDEntries>

//        String query = MessageFormat.format(
//                "<{0}> '{' for $xsd in distinct-values(/{0}/{1}/fileName/) return <XSD> <fileName>'{' $xsd '}'</fileName> </XSD> '}' </{0}>", XSDEntries.class.getSimpleName(), XSD.class.getSimpleName());
//        
        String query = MessageFormat.format(
                "<{0}> '{' for $xsd in distinct-values(/{0}/{1}/fileName/text()) return (/{0}/{1}[fileName = $xsd])[last()] '}' </{0}>", XSDEntries.class.getSimpleName(), XSD.class.getSimpleName());

        try {
            baseXResult = new XQuery(query).execute(baseXContext);
        } catch (BaseXException ex) {
            Logger.getLogger(XSDDaoImpl.class.getName()).log(Level.SEVERE, "BaseX query failed in finding all queries.", ex);
        }

        //empty database
        if (baseXResult.equals("")) {
            return new ArrayList<XSD>();
        }

        XSDEntries ret = (XSDEntries) xstream.fromXML(baseXResult);

        return ret.list;
    }

    @Override
    public Object getAllEntriesByFileName(String fileName) {
        String baseXResult = "";

        String query = MessageFormat.format(
                "<{0}> '{' for $xsd in /{0}/{1} where $xsd/fileName = ''{2}''  return $xsd '}' </{0}>", XSDEntries.class.getSimpleName(), XSD.class.getSimpleName(), fileName);

        try {
            baseXResult = new XQuery(query).execute(baseXContext);
        } catch (BaseXException ex) {
            Logger.getLogger(XSDDaoImpl.class.getName()).log(Level.SEVERE, "BaseX query failed in finding all queries.", ex);
        }

        //empty database
        if (baseXResult.equals("")) {
            return new ArrayList<XSD>();
        }

        XSDEntries ret = (XSDEntries) xstream.fromXML(baseXResult);

        return ret.list;
    }

    @Override
    public Object getEntryByFileNameAndTimeStamp(String fileName, String timeStamp) {
        String baseXResult = "";

        String query = MessageFormat.format(
                "<{0}> '{' for $xsd in /{0}/{1} where $xsd/fileName = ''{2}'' and $xsd/timeStamp = {3}  return $xsd '}' </{0}>", XSDEntries.class.getSimpleName(), XSD.class.getSimpleName(), fileName, timeStamp);

        try {
            baseXResult = new XQuery(query).execute(baseXContext);
        } catch (BaseXException ex) {
            Logger.getLogger(XSDDaoImpl.class.getName()).log(Level.SEVERE, "BaseX query failed in finding all queries.", ex);
        }

        //empty database
        if (baseXResult.equals("")) {
            return new ArrayList<XSD>();
        }

        XSDEntries ret = (XSDEntries) xstream.fromXML(baseXResult);

        return ret.list.get(0);
    }

    @Override
    public String printAll() {
        try {
            return new XQuery("/").execute(baseXContext);
        } catch (BaseXException ex) {
            // Logger.getLogger(XSDDaoImpl.class.getName()).log(Level.SEVERE, null, ex);
        }
        return "#EMPTY#";
    }

    private void setxStreamAliases() {
        xstream.alias(XSD.class.getSimpleName(), XSD.class);
        xstream.alias(XSDEntries.class.getSimpleName(), XSDEntries.class);
        xstream.alias(XSD.SimpleType.class.getSimpleName(), XSD.SimpleType.class);
        xstream.alias(XSD.ComplexType.class.getSimpleName(), XSD.ComplexType.class);
        xstream.addImplicitCollection(XSDEntries.class, "list");
    }

    @Override
    public void deleteAllVersionsXsdEntryByName(String fileName) {

        String query = MessageFormat.format(
                "delete node /{0}/{1}[fileName = ''{2}'']", XSDEntries.class.getSimpleName(), XSD.class.getSimpleName(), fileName);

        QueryProcessor processor = new QueryProcessor(query, baseXContext);
        try {
            processor.execute();
        } catch (QueryException ex) {
            Logger.getLogger(XSDDaoImpl.class.getName()).log(Level.SEVERE, "Can't delete XSD enstries by specified filename: " + fileName, ex);
        }
    }

    @Override
    public void deleteXsdEntryTimeStamp(String timeStamp) {

        String query = MessageFormat.format(
                "delete node /{0}/{1}[timeStamp = {2}]", XSDEntries.class.getSimpleName(), XSD.class.getSimpleName(), timeStamp);

        QueryProcessor processor = new QueryProcessor(query, baseXContext);
        try {
            processor.execute();
        } catch (QueryException ex) {
            Logger.getLogger(XSDDaoImpl.class.getName()).log(Level.SEVERE, "Can't delete XSD enstry by specified timestamp: " + timeStamp, ex);
        }
    }

    @Override
    public XSD getEntryByTimestamp(String timeStamp) {
        String baseXResult = "";

        String query = MessageFormat.format(
                "<{0}> '{' for $xsd in /{0}/{1} where $xsd/timeStamp = {2}  return $xsd '}' </{0}>", XSDEntries.class.getSimpleName(), XSD.class.getSimpleName(), timeStamp);

        try {
            baseXResult = new XQuery(query).execute(baseXContext);
        } catch (BaseXException ex) {
            Logger.getLogger(XSDDaoImpl.class.getName()).log(Level.SEVERE, "BaseX query failed in finding all queries.", ex);
        }

        //empty database
        if (baseXResult.equals("")) {
            return null;
        }

        XSDEntries ret = (XSDEntries) xstream.fromXML(baseXResult);

        return ret.list.get(0);
    }

    @Override
    public Object getAllEntriesDistinctNamesByFileName(String fileNameInput) {
        String baseXResult = "";

        String query = MessageFormat.format(
                "<{0}> '{' for $xsd in distinct-values(/{0}/{1}/fileName[contains(text(), ''{2}'')]) return (/{0}/{1}[fileName = $xsd])[last()] '}' </{0}>", XSDEntries.class.getSimpleName(), XSD.class.getSimpleName(), fileNameInput);

        try {
            baseXResult = new XQuery(query).execute(baseXContext);
        } catch (BaseXException ex) {
            Logger.getLogger(XSDDaoImpl.class.getName()).log(Level.SEVERE, "BaseX query failed in finding all queries.", ex);
        }

        //nothing found
        if (baseXResult.equals("")) {
            return new ArrayList<XSD>();
        }

        XSDEntries ret = (XSDEntries) xstream.fromXML(baseXResult);

        return ret.list;
    }

    @Override
    public Object getAllEntriesDistinctNamesBySimpleTypeContains(String typeNameInput) {
        List<XSD> allEntries = (List<XSD>) getAllEntriesDistinctNames();
        List<XSD> ret = new ArrayList<>();

        if (allEntries == null) {
            return ret;
        }

        for (XSD item : allEntries) {
            for (XSD.Type simpleType : item.getSimpleTypes()) {
                if (simpleType.getName().contains(typeNameInput)) {
                    ret.add(item);
                    break;
                }
            }
        }

        return ret;
    }

    @Override
    public Object getAllEntriesDistinctNamesByComplexTypeContains(String typeNameInput) {
        List<XSD> allEntries = (List<XSD>) getAllEntriesDistinctNames();
        List<XSD> ret = new ArrayList<>();

        if (allEntries == null) {
            return ret;
        }

        for (XSD item : allEntries) {
            for (XSD.Type complexType : item.getComplexTypes()) {
                if (complexType.getName().contains(typeNameInput)) {
                    ret.add(item);
                    break;
                }
            }
        }

        return ret;
    }

    @Override
    public Object getAllEntriesDistinctNamesByFileNameAndSimpleType(String fileNameInput, String typeNameInput) {
        List<XSD> allEntries = (List<XSD>) getAllEntriesDistinctNamesByFileName(fileNameInput);
        List<XSD> ret = new ArrayList<>();

        if (allEntries == null) {
            return ret;
        }

        for (XSD item : allEntries) {
            for (XSD.Type simpleType : item.getSimpleTypes()) {
                if (simpleType.getName().contains(typeNameInput)) {
                    ret.add(item);
                    break;
                }
            }
        }

        return ret;

    }

    @Override
    public Object getAllEntriesDistinctNamesByFileNameAndComplexType(String fileNameInput, String typeNameInput) {
        List<XSD> allEntries = (List<XSD>) getAllEntriesDistinctNamesByFileName(fileNameInput);
        List<XSD> ret = new ArrayList<>();

        if (allEntries == null) {
            return ret;
        }

        for (XSD item : allEntries) {
            for (XSD.Type complexType : item.getComplexTypes()) {
                if (complexType.getName().contains(typeNameInput)) {
                    ret.add(item);
                    break;
                }
            }
        }

        return ret;
    }

    @Override
    public Object getAllEntriesByFileNameAndSimpleTypeContains(String fileName, String typeNameInput) {
        List<XSD> allEntries = (List<XSD>) getAllEntriesByFileName(fileName);
        List<XSD> ret = new ArrayList<>();

        if (allEntries == null) {
            return ret;
        }

        for (XSD item : allEntries) {
            for (XSD.Type simpleType : item.getSimpleTypes()) {
                if (simpleType.getName().contains(typeNameInput)) {
                    ret.add(item);
                    break;
                }
            }
        }
        return ret;
    }

    @Override
    public Object getAllEntriesByFileNameAndComplexTypeContains(String fileName, String typeNameInput) {
        List<XSD> allEntries = (List<XSD>) getAllEntriesByFileName(fileName);
        List<XSD> ret = new ArrayList<>();

        if (allEntries == null) {
            return ret;
        }
        for (XSD item : allEntries) {
            for (XSD.Type complexType : item.getComplexTypes()) {
                if (complexType.getName().contains(typeNameInput)) {
                    ret.add(item);
                    break;
                }
            }
        }
        return ret;
    }

    private class XSDEntries {

        private List<XSD> list;

        public XSDEntries() {
            list = new ArrayList<XSD>();
        }

        public void add(XSD xsd) {
            list.add(xsd);
        }

        public List<XSD> getList() {
            return list;
        }

        public void setList(List<XSD> list) {
            this.list = list;
        }

    }

}
